LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

UnityShader入门精要②(UnityShader基础)

2021/8/28

本文为学习UnityShader时所记录的笔记,供学习产出和日后复习使用。

学习资料为冯乐乐(问就是我女神)的《UnityShader入门精要》

呜呜呜今天看书看这节的时候已经感觉人开始略微劝退了,不过还是充满激情的!


3.1 UnityShader概述

在unity中,Shader需要和材质配合使用。

unity中提供了四种Shader模板:

Standard Surface Shader:包含了标准光照模型的表面着色器模板

Unlit Shader:不包含标准光照的基本顶点/片元着色器(教程中大部分使用)

Image Effect Shader:实现各种屏幕后处理效果

Compute Shader:产生特殊Shader文件来利用GPU并行性进行与渲染无关的计算(一般用不到)


3.2 UnityShaderLab

UnityShaderLab是专门用来为UnityShader服务的语言,是高级层的渲染抽象层。

一个UnityShader基础结构如下:

Shader "ShaderName"
{
Properties
{//属性}

SubShader
{//子着色器A}

SubShader
{//子着色器B}

Fallback “VertexLit”
}

unity会根据目标平台将以上的UnityShaderLab代码编译成真正的代码和Shader文件。


3.3 UnityShader的结构

起个名字

每个Unity Shader 第一行都需要通过Shader语义来指定该Unity Shader的名字。

Shader "MyShader"

或者是这样

Shader "Custom/MyShader"

在字符串中添加斜杠可以控制Shader在材质面板出现的位置


Properties

Propertie是材质和UnityShader的桥梁,他包含了一系列属性,这些语义会出现在材质面板中。

Properties语义块定义一般如下:

Properties
{
Name ("display name",PropertyType)=DefaultValue
}

声明这些属性是为了在材质面板中能方便的调整。

若需要在Shader中访问这些属性,则需要使用每个属性的名字(Name)一般由下划线开始。

显示的名称(display name)是出现在材质面板上面的名字。

我们还需要为每个属性指定他的类型(PropertyType)并指定一个默认值

下列代码展示了所有的属性:

Shader "Custom/MyShad类型er"
{
    Properties
    {
        //number and slider
        _MainTex("Texture", 2D) = "white" {}
        _Int("Int",Int) = 2
        _Float("Float",Float) = 1.5
        _Range("Range",Range(0.0,5.0)) = 3.0

            //color and vector
            _Color("Color",Color) = (1,1,1,1)
            _Vector("Vector",Vector) = (2,3,6,1)

            //texture
            _2D("2D",2D) = ""{ }
            _3D("3D",3D) = "Black"{ }
            _Cube("Cube",Cube) = "White"{}

    }
        FallBack "Diffuse" 

对于Int Float Range这些数字类型的属性,其默认值是一个单独的数字。

对于Color和Vector这类属性,默认值是圆括号包围的四维向量。

对于2D,Cube,3D这类属性,默认值是一个字符串跟着一个花括号。

字符串要么是空的要么是内置的纹理名称(white,black,yellow)


SubShader

每一个Unity Shader文件可以包含多个SubShader语义块,但最少得有一个。

Unity会扫描所有语义块,选择第一个能在目标平台运行的语义块。若都不支持则会使用Fallback语义指定的Unity Shader。

原因是这样可以供不同性能的显卡使用不同复杂程度的语义块。

SubShader语义块包含的定义通常如下:

SubShader
{
//可选的标签设置
[Tags]

//可选的状态设置
[RenderSetup]

Pass{
}
//其他pass
}

SubShader中提供了一系列Pass和可选的状态设置(tag)和标签设置(RenderSetup)。

每个Pass定义了一套完整的渲染流程,但如果Pass过多就好造成渲染性能的下降。应尽量使用尽可能少数目的Pass。

Tags和RenderSetup可在SubShader中定义,若在SubShader中定义则会用于所有Pass;也可以在Pass中单独定义。

其中Tags有些特定设置,在SubShader和Pass是不一样的。而RenderSetup的语法则完全相同。


状态设置(RenderSetup):

ShaderLab提供了一系列渲染状态的设置指令,用于设置显卡的各种状态。

例如:

Cull Back | Front |Off (剔除背面/正面/关闭)

ZTest 巴拉巴拉(设置深度测试使用的函数)

ZWrite On | Off(开启关闭深度写入)

Blend 巴拉巴拉(开启并设置混合模式)

在SubShader中设置以上渲染状态时,将会应用到所有的Pass。

若不想这样也可以在Pass中单独进行以上设置。


标签设置(Tags):

Tags是一个键值对,值和键都是字符串类型

Tags{"TagName1"="Value1" "TagName2"="Value2"}

标签有以下(具体的键对应的值上网查吧太懒了555):

Queue(控制渲染顺序,指定该物体属于哪一个渲染队列)

RenderType(对着色器进行分类)

DisableBatching(是否禁用批处理)

ForceNoShadowCasting(控制该物体是否会投射阴影)

IgnoreProjector(是否受Projector影响,通常用于半透明物体)

CanUseSpriteAtlas(当SubShader用于精灵时将这个设为false)

PreviewType(指明材质面板该如何预览该材质,Plane,SkyBox)

上面的标签只能在SubShader中声明,而不能在Pass内。

Pass中声明的标签不同于SubShader的标签类型。


Pass语义块:

Pass语义块定义如下

Pass{
[Name]
[Tags]
[RenderType]
//Other Type

首先可以定义名字

例如Name "MyPassName"这样

通过这个名字,可以使用UsePass命令来直接使用其他Unity Shader的Pass。

例如:

UsePass "MyShader/MYPASSNAME"

这样能提高代码的复用性,注意Unity内部会把所有Pass的名字转换为大写,因此使用UsePass命令时必须使用大写的名字。

Pass可以单独设置状态,这个在上面提到过了,用的是和在SubShader里面一样的状态类型。

但Pass内的标签就不同与SubShader的标签了,Pass内使用的是:

LightMode(定义该Pass在渲染流水线的角色)

RequireOptions(用于指定满足某些条件时才渲染该pass)


Fallback

Fallback是我们的后路,紧跟在SubShader语义块之后。

意味着所有的SubShader都不能在这张显卡上运行的情况下,使用Fallback指定的最低级的Shader。

Fallback "Name"
//或者
Fallback Off

Fallback还会影响阴影的投射,所以正确的设置Fallback是很重要的。


3.4 UnityShader的形式

我们可以用表面着色器(Surface Shader),顶点/片元着色器(Vertex/Fragment Shader),固定函数着色器(Fixed Function Shader)的形式来编写UnityShader。

其中固定函数着色器很少使用,所以略掉。

表面着色器(Surface Shader)

表面着色器是Unity自己创造的一种着色器代码类型,渲染的代价比较大。

本质上和顶点/片元着色器相同,实际上unity也在背后转换成顶点/片元着色器。

可以理解为表面着色器是顶点/片元着色器更高一层的抽象,并为我们处理了很多光照细节。

SubShader{
Tags{"RenderType"="Opaque"}
CGPROGRAM
#pragma surface surf Lambert
struct Input{
 float 4 color : COLOR;
};
void surf (Input IN,inout SurfaceOutput o){
o.Albedo=1;
}
ENDCG
}

表面着色器被定义在SubShader语义块(非pass内),不需要关心使用了多少个Pass,Unity会在背后为我们处理好一切。

CGPROGRAM和ENDCG之间的代码使用Cg/HLSL编写,相当于将Cg/HLSL嵌套在ShaderLab中,语法和标准Cg/HLSL几乎一致。


顶点/片元着色器(Vertex/Fragment Shader)

以下代码来源于Unity新建的Unlit Shader模板。

现在可以不用看懂,只关注语义框架。

 SubShader
            {
                Pass
                {
                    CGPROGRAM
                    #pragma vertex vert
                    #pragma fragment frag
                    #pragma multi_compile_fog
                    #include "UnityCG.cginc"

                    struct appdata
                    {
                        float4 vertex : POSITION;
                        float2 uv : TEXCOORD0;
                    };

                    struct v2f
                    {
                        float2 uv : TEXCOORD0;
                        UNITY_FOG_COORDS(1)
                        float4 vertex : SV_POSITION;
                    };

                    sampler2D _MainTex;
                    float4 _MainTex_ST;

                    v2f vert(appdata v)
                    {
                        v2f o;
                        o.vertex = UnityObjectToClipPos(v.vertex);
                        o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                        UNITY_TRANSFER_FOG(o,o.vertex);
                        return o;
                    }

                    fixed4 frag(v2f i) : SV_Target
                    {
                        // sample the texture
                        fixed4 col = tex2D(_MainTex, i.uv);
                    // apply fog
                    UNITY_APPLY_FOG(i.fogCoord, col);
                    return col;
                }
                ENDCG
            }
            }

顶点/片元着色器代码也需要定义在CGPROGRAM和ENDCG之间

但顶点/片元着色器需要写在Pass内,而非SubShader内

因为与表面着色器不同,此处我们需要自己单独定义每个pass的Shader代码。

虽然这样需要编写更多代码,但灵活性也更高,我们可以由此控制渲染细节。

这里CGPROGRAM和ENDCG之间的代码也使用Cg/HLSL编写。


如何选择

  • 除非在非常旧的设备(iPhone3)上运行,否则一般使用可编程管线的着色器而不是固定管线着色器。
  • 若希望和各类光源打交道,不妨试试表面着色器(小心移动平台的性能表现)
  • 若所需光源数目少(例如只有一个平行光),则使用顶点/片元着色器是更好的选择。
  • 若需要很多自定义的渲染效果,则也请使用顶点/片元着色器。


昨天中间跑去约会又去酒吧爽喝,又睡了个大觉,拖下来十几个小时才完成这些,不过这两天还是蛮开心的!2021.8.29 16:24